package com.gitee.mall.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.gitee.common.to.mq.SeckillOrderTo;
import com.gitee.common.utils.R;
import com.gitee.common.vo.MemberRespVo;
import com.gitee.mall.seckill.feign.CouponFeignService;
import com.gitee.mall.seckill.feign.ProductFeignService;
import com.gitee.mall.seckill.interceptor.LoginUserInterceptor;
import com.gitee.mall.seckill.service.SeckillService;
import com.gitee.mall.seckill.to.SecKillSkuRedisTo;
import com.gitee.mall.seckill.vo.SeckillSesssionsWithSkus;
import com.gitee.mall.seckill.vo.SeckillSkuVo;
import com.gitee.mall.seckill.vo.SkuInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;


@Slf4j
@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    private CouponFeignService couponFeignService;
    @Autowired
    private ProductFeignService productFeignService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private StringRedisTemplate strRedisTemplate;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    private final String SKUKILL_CACHE_PREFIX = "seckill:skus";
    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";//+商品随机码
    private final String SESSIONS_CACHE_PREFIX = "seckill:sessions:";


    /**
     * 返回当前时间可以参与的秒杀商品信息
     *
     * @return
     */
    @Override
    public List<SecKillSkuRedisTo> getCurrentSeckillSkus() {
        //1、确定当前时间属于哪个秒杀场次。
        long time = new Date().getTime();
        Set<String> keys = strRedisTemplate.keys(SESSIONS_CACHE_PREFIX + "*");
        for (String key : keys) {
            //seckill:sessions:1582250400000_1582254000000
            String replace = key.replace(SESSIONS_CACHE_PREFIX, "");
            String[] s = replace.split("_");
            Long start = Long.parseLong(s[0]);
            Long end = Long.parseLong(s[1]);
            if (time >= start && time <= end) {
                //2、获取这个秒杀场次需要的所有商品信息
                List<String> range = strRedisTemplate.opsForList().range(key, -100, 100);
                BoundHashOperations<String, String, String> hashOps = strRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                List<String> list = hashOps.multiGet(range);
                if (list != null) {
                    List<SecKillSkuRedisTo> collect = list.stream().map(item -> {
                        SecKillSkuRedisTo redis = JSON.parseObject((String) item, SecKillSkuRedisTo.class);
//                        redis.setRandomCode(null); 当前秒杀开始就需要随机码
                        return redis;
                    }).collect(Collectors.toList());
                    return collect;
                }
                break;
            }
        }

        return null;
    }


    @Override
    public SecKillSkuRedisTo getSkuSeckillInfo(Long skuId, String sessionId) {
        //1、找到所有需要参与秒杀的商品的key
        BoundHashOperations<String, String, String> hashOps = strRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX + sessionId);
        String redisField = sessionId + skuId;
        String redisToStr = hashOps.get(redisField);
        if (StringUtils.isBlank(redisToStr)) {
            return null;
        }

        SecKillSkuRedisTo redisTo = JSON.parseObject(redisToStr, SecKillSkuRedisTo.class);
        long nowTime = new Date().getTime();
        if (redisTo.getEndTime() < nowTime) {       //当前场次商品已过期
            hashOps.delete(redisField);
            redisTo.setRandomCode(null);
        }

        if (nowTime >= redisTo.getStartTime() && redisTo.getEndTime() > nowTime) {
            return redisTo;
        }

        return null;
    }

    /**
     * 秒杀下单  立即抢购
     *
     * @param hashField hashFiled（sessionId+skuid）
     * @param sessionId 场次id
     * @param token     校验码
     * @param num       购买数量
     */
    @Override
    public String secKill(String hashField, String sessionId, String token, Integer num) {
        MemberRespVo respVo = LoginUserInterceptor.loginUser.get();
        //获取缓存hash中的数据
        BoundHashOperations<String, String, String> hashOps = strRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX + sessionId);
        String redisToStr = hashOps.get(hashField);
        if (redisToStr == null) {
            return "没有当前商品";
        }


        SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(redisToStr, SecKillSkuRedisTo.class);
        //校验时间合法性
        long time = new Date().getTime();
        if (secKillSkuRedisTo.getStartTime() > time || secKillSkuRedisTo.getEndTime() < time) {
            return "秒杀时间不合法";
        }
        //校验随机码
        if (!token.equals(secKillSkuRedisTo.getRandomCode())) {
            return "秒杀时间不合法";
        }
        if (num > secKillSkuRedisTo.getSeckillCount()) {
            return "秒杀商品数量超限";
        }

        //没人限购数量
        String redisUserKey = respVo.getId() + "" + secKillSkuRedisTo.getSkuId();
        if (strRedisTemplate.opsForValue().increment(redisUserKey, num) > secKillSkuRedisTo.getSeckillLimit()) {
            return "秒杀商品数量超限";
        }
        //设置过期时间
        strRedisTemplate.expireAt(redisUserKey, new Date(secKillSkuRedisTo.getEndTime()));
        //扣钱库存
        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
        boolean acquire = semaphore.tryAcquire(num);
        if (!acquire) {
            return "秒杀失败";
        }

        //快速下单。发送MQ消息  10ms
        String timeId = IdWorker.getTimeId();
        SeckillOrderTo orderTo = new SeckillOrderTo();
        orderTo.setOrderSn(timeId);
        orderTo.setMemberId(respVo.getId());
        orderTo.setNum(num);
        orderTo.setPromotionSessionId(secKillSkuRedisTo.getPromotionSessionId());
        orderTo.setSkuId(secKillSkuRedisTo.getSkuId());
        orderTo.setSeckillPrice(secKillSkuRedisTo.getSeckillPrice());
        rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", orderTo);

        return timeId;
    }

    @Override
    public void uploadSeckillSkuLatest3Days() {
        R lates3DaySession = couponFeignService.getLates3DaySession();
        if (lates3DaySession.getCode() == 0) {
            List<SeckillSesssionsWithSkus> sessionDatas = lates3DaySession.getData(new TypeReference<List<SeckillSesssionsWithSkus>>() {
            });
            if (CollectionUtils.isEmpty(sessionDatas)) {
                return;
            }

            //保存到redis
            //1、缓存活动信息
            saveSessionInfos(sessionDatas);

            //2、缓存活动的关联商品信息
            saveSessionSkuInfos(sessionDatas);
        }
    }

    /**
     * 缓存 list类型
     * 前缀+start_time +"_"+ end_time,List
     *
     * @param sessionDatas sessionDatas
     */
    private void saveSessionInfos(List<SeckillSesssionsWithSkus> sessionDatas) {
        sessionDatas.forEach(sesssion -> {
            long startTime = sesssion.getStartTime().getTime();
            long endTime = sesssion.getEndTime().getTime();
            String key = SESSIONS_CACHE_PREFIX + startTime + "_" + endTime;
            Boolean hasKey = strRedisTemplate.hasKey(key);      //TODO 该方法性能很差
            if (Boolean.TRUE.equals(hasKey)) {
                return;
            }

            List<String> collect = sesssion.getRelationSkus().stream().map(item -> item.getPromotionSessionId() + "_" + item.getSkuId().toString()).collect(Collectors.toList());
            //缓存活动信息
            strRedisTemplate.opsForList().leftPushAll(key, collect);
            strRedisTemplate.expireAt(key, new Date(endTime));
        });
    }


    /**
     * 缓存活动关联的产品信息
     * <p>
     * hash:<前缀+场次id，场次id+skuId, sku信息>
     * TODO 为了保证原子性，可以使用lua脚本
     *
     * @param sessionDatas 最近3天需要上架的所有场次及关联商品
     */
    private void saveSessionSkuInfos(List<SeckillSesssionsWithSkus> sessionDatas) {
        sessionDatas.forEach(sessionData -> {
            List<SeckillSkuVo> relationSkus = sessionData.getRelationSkus();
            if (CollectionUtils.isEmpty(relationSkus)) {
                return;
            }

            String hashKey = SKUKILL_CACHE_PREFIX + sessionData.getId();
            BoundHashOperations<String, Object, Object> hashOps = strRedisTemplate.boundHashOps(hashKey);
            for (SeckillSkuVo seckillSkuVo : relationSkus) {
                //本场次已经添加过 不再添加
                String hashField = seckillSkuVo.getPromotionSessionId() + "_" + seckillSkuVo.getSkuId();
                if (Boolean.TRUE.equals(hashOps.hasKey(hashField))) {
                    continue;
                }

                //1、商品的随机码
                String token = UUID.randomUUID().toString().replace("-", "");
                //2、商品的秒杀信息封装到Redis
                SecKillSkuRedisTo redisTo = new SecKillSkuRedisTo();
                //3、sku的基本数据
                R skuInfoR = productFeignService.getSkuInfo(seckillSkuVo.getSkuId());
                if (skuInfoR.getCode() == 0) {
                    SkuInfoVo skuInfo = skuInfoR.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                    });
                    redisTo.setSkuInfo(skuInfo);
                }
                BeanUtils.copyProperties(seckillSkuVo, redisTo);
                redisTo.setStartTime(sessionData.getStartTime().getTime());
                redisTo.setEndTime(sessionData.getEndTime().getTime());
                redisTo.setRandomCode(token);

                String jsonString = JSON.toJSONString(redisTo);
                hashOps.put(hashField, jsonString);

                //5、使用库存作为分布式的信号量  限流；
                RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                semaphore.trySetPermits(seckillSkuVo.getSeckillCount());
                semaphore.expireAt(sessionData.getEndTime());
            }

            //设置过期时间
            strRedisTemplate.expireAt(SKUKILL_CACHE_PREFIX + sessionData.getId(), sessionData.getEndTime());
        });
    }


}
